﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using ComHlp.ContentClasses;
using ComHlp.enums;

namespace ComHlp.Classes.Net
{
    [SuppressMessage("ReSharper", "ExceptionNotDocumented")]
    internal static class CompressionHelper
    {
        /// <summary> Uncompress the received message </summary>
        /// <param name="data"></param>
        /// <param name="serverMessage"></param>
        /// <param name="recvBufferBase"></param>
        /// <returns></returns>
        internal static byte[] UnCompressMessage(byte[] data, ItxServerMessage serverMessage, ref byte[] recvBufferBase)
        {
            // reset message - it is later uninteresting if compressed or not
            serverMessage.MessageType = ItxServerMessageType.ScreenContent;

            // build byte array without header
            byte[] compressed = new byte[data.Length - 70];
            Array.Copy(data, 70, compressed, 0, data.Length - 70);

            // temp byte array, containg the uncompressed data/later the expanded byte[]
            byte[] uncompressed;

            // at first, we need to 'unzip' 
            using (MemoryStream memoryStream = new MemoryStream())
            {
                // skip server header (pos 70)
                for (int i = 0; i < compressed.Length;)
                {
                    byte recvByte = compressed[i];
                    if (recvByte == 0x00)
                    {
                        memoryStream.WriteByte(0x00);
                        break;
                    }

                    // got string length or repetition?
                    if (recvByte < 0x80)
                    {
                        // repetition found, so we need to repeat the next byte n times
                        byte repetitionByte = compressed[i + 1];
                        for (int j = 0; j < recvByte; j++)
                            memoryStream.WriteByte(repetitionByte);
                        // advance by length and repetition byte
                        i = i + 2;
                        continue;
                    }

                    // we got a string length, let us calculate it at first and then write into memorystream
                    int length = recvByte - 0x80;
                    memoryStream.Write(compressed, i + 1, length);
                    i = i + 1 + length;
                }

                uncompressed = memoryStream.ToArray();
            }

            // uncompressed - build comparison
            using (MemoryStream msCalculated = new MemoryStream())
            {
                for (int i = 0; i < uncompressed.Length; i++)
                {
                    // subtract
                    int byteDiff = uncompressed[i] + recvBufferBase[i];

                    // if bigger than 255 substract 256
                    if (byteDiff > 255)
                        byteDiff -= 256;

                    // convert ushort back to byte 
                    byte[] tmpByte = BitConverter.GetBytes((ushort) byteDiff);

                    // save important byte in temp data array
                    msCalculated.WriteByte(tmpByte[0]);
                }

                uncompressed = msCalculated.ToArray();
            }

            // save receive buffer
            recvBufferBase = uncompressed;

            //
            // create new byte data including the server header
            byte[] tmpDataArray = new byte[uncompressed.Length + 70];
            // server header
            Array.Copy(data, 0, tmpDataArray, 0, 70);
            // rest of data
            Array.Copy(uncompressed, 0, tmpDataArray, 70, uncompressed.Length);

            //
            // set back to data
            data = tmpDataArray;
            return data;
        }

        /// <summary> Compress message to send </summary>
        /// <param name="data"></param>
        /// <param name="sendBufferBase"></param>
        /// <returns></returns>
        internal static byte[] CompressMessage(byte[] data, ref byte[] sendBufferBase)
        {
            /* description:
             * - at the very first, we need to calculate the difference between the saved buffer and the new buffer
             * - compare each byte with the next byte until the next byte is different 
             *   if the next byte is not different, we need to count how many bytes are 'same' to get the repeation count
             *   after we have got the repeation cound we need to convert the count to hex and insert the repeation byte afterwards
             * - if the next byte is directly different we handle it as 'string'. so we need to get the number of different bytes
             *   until the following byte is different again and set the string length as converted to hex before we send the bytes as 
             *   they are
             */

            /* at very first, build the comparison by subtracting the saved buffer with the new datas to send
             */
            byte[] calculatedBuffer;
            using (MemoryStream msCalculated = new MemoryStream())
            {
                for (int i = 70; i < data.Length; i++)
                {
                    // subtract
                    byte dataByte = data[i];
                    byte sendByte = sendBufferBase[i - 70];

                    int byteDiff = dataByte - sendByte;
                    // if difference if negative, add 256
                    if (byteDiff < 0)
                        byteDiff = byteDiff + 256;

                    // convert ushort back to byte 
                    byte[] tmpLength = BitConverter.GetBytes((ushort) byteDiff);

                    // save important byte in temp data array
                    msCalculated.WriteByte(tmpLength[0]);
                }

                // save receive buffer
                //Array.Copy(msCalculated.ToArray(), 0, sendBufferBase, 0, msCalculated.Length); 
                calculatedBuffer = msCalculated.ToArray();
            }

            // save the compare buffer (better, save the data array as buffer)
            Array.Copy(data, 70, sendBufferBase, 0, sendBufferBase.Length);

            //
            // now we need analyze for repetition 
            int stringLengthStartPos = -1;
            using (MemoryStream msFinal = new MemoryStream())
            {
                for (int i = 0; i < calculatedBuffer.Length; i++)
                {
                    // at the end?
                    if (i + 1 == calculatedBuffer.Length)
                    {
                        // got string before which we need to handle here also?
                        HandleCompressionStringInsertion(msFinal, ref stringLengthStartPos, i, calculatedBuffer);
                        break;
                    }

                    //
                    // is the next byte different or have we got a repetition?
                    byte curByte = calculatedBuffer[i];
                    byte nextByte = calculatedBuffer[i + 1];
                    if (curByte == nextByte)
                    {
                        // got string before?
                        HandleCompressionStringInsertion(msFinal, ref stringLengthStartPos, i, calculatedBuffer);

                        //
                        // check how long the repetition does last, starting from next byte
                        int j = i + 1;
                        for (; j < calculatedBuffer.Length; j++)
                        {
                            if (curByte != calculatedBuffer[j])
                                break;
                        }

                        // calculate the amount of repetition 
                        int repetitionAmount = j - i;

                        // advance current bytearray position
                        i = j - 1;

                        // maximum of 128 (0x80) repetition possible
                        if (repetitionAmount >= 127)
                        {
                            while (repetitionAmount > 0)
                            {
                                int curLength = repetitionAmount >= 127 ? 127 : repetitionAmount;

                                // insert the repetition into the final array including the length
                                var tmpLength = BitConverter.GetBytes((ushort) curLength);
                                // save length and the byte itself in the new array
                                msFinal.WriteByte(tmpLength[0]);
                                msFinal.WriteByte(curByte);

                                repetitionAmount -= curLength;
                            }
                        }
                        else
                        {
                            // insert the repetition into the final array including the length
                            var tmpLength = BitConverter.GetBytes((ushort) repetitionAmount);
                            // save length and the byte itself in the new array
                            msFinal.WriteByte(tmpLength[0]);
                            msFinal.WriteByte(curByte);
                        }
                    }
                    else
                    {
                        // already determined that a string is following?
                        if (stringLengthStartPos == -1)
                            stringLengthStartPos = i;
                    }
                }

                // finalization - set sending buffer and copy back to data
                byte[] finalData = new byte[msFinal.Length + 70];
                Array.Copy(data, 0, finalData, 0, 70);

                Array.Copy(msFinal.ToArray(), 0, finalData, 70, msFinal.Length);
                data = finalData;
            }

            return data;
        }

        /// <summary> Handle string insertion in compress buffer </summary>
        /// <param name="msFinal"></param>
        /// <param name="stringLengthStartPos"></param>
        /// <param name="currentBytePos"></param>
        /// <param name="sendBufferBase"></param>
        private static void HandleCompressionStringInsertion(MemoryStream msFinal, ref int stringLengthStartPos, int currentBytePos, byte[] sendBufferBase)
        {
            //
            // handle stringlength in advance?
            if (stringLengthStartPos == -1)
                return;

            // calculate length of string
            int stringLength = currentBytePos - stringLengthStartPos;

            // add 0x80 == decimal 128 to the length for indicating it is a length
            int compressedStringLength = stringLength + 128;

            // convert length
            byte[] tmpLength = BitConverter.GetBytes((ushort) compressedStringLength);

            // save length and the 'string' itself in the new array
            msFinal.WriteByte(tmpLength[0]);
            msFinal.Write(sendBufferBase, stringLengthStartPos, stringLength);

            // reset startpos
            stringLengthStartPos = -1;
        }
    }
}